﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Practices.Unity;
using Sagacious.DataSources;
using Sagacious.Entities;

namespace Sagacious.Services
{
    /// <summary>
    /// The LogFileWatcher class monitors a log file for changes, updates the
    /// data source when a change occurs, and raises an event whenever log items
    /// are added to the data source.
    /// </summary>
    public class LogFileWatcher :  ILogWatcher
    {
        #region Constructors
        /// <summary>
        /// Creates a new LogFileWather object.
        /// </summary>
        /// <param name="filename">The path to the log file to be monitored.</param>
        public LogFileWatcher(string filename)
        {
            if (String.IsNullOrEmpty(filename))
            {
                throw new ArgumentNullException();
            }
            if (!File.Exists(filename))
            {
                throw new ArgumentException();
            }

            // Get the data source from the dependency resolver.
            dataSource = DependencyContainer.Container.Resolve<ILogEntryDataSource>();

            // Set the local file name and get the host name for the file.
            this.filename = filename;
            if (this.FileIsRemote)
            {
                Uri uri = new Uri(filename);
                hostName = uri.Host;
            }
            else
            {
                hostName = Dns.GetHostName();
            }
            // Create a FileSystemWatcher object to monitor changes to the file.
            fileWatcher = new FileSystemWatcher(Path.GetDirectoryName(this.filename), Path.GetFileName(this.filename));

            // Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories.
            fileWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            fileWatcher.Changed += fileWatcher_Changed;
            fileWatcher.Created += fileWatcher_Created;
            fileWatcher.Deleted += fileWatcher_Deleted;
            fileWatcher.Renamed += fileWatcher_Renamed;
        }
        #endregion

        #region Event Handlers
        
        /// <summary>
        /// This event is raised whenever the watcher adds a log entry to the 
        /// data source.
        /// </summary>
        public event EventHandler LogChanged;

        #endregion Event Handlers

        #region Private Methods

        /// <summary>
        /// 
        /// </summary>
        private void ReadNewLogEntries()
        {
            bool logEntryAdded = false;

            // Open file for reading
            using (StreamReader _reader = this.OpenLogFile())
            {
                // Seek to the last position read in the file
                if (filePosition > 0)
                {
                    _reader.BaseStream.Seek(filePosition, SeekOrigin.Begin);
                }
                // Read to the end of the file
                while (!_reader.EndOfStream)
                {
                    // Read and parse the next line in the file
                    // var _line = _reader.ReadLine();
                    LogEntry entry = ParseLogEntry(_reader.ReadLine());
                    // Lock the dataSource to prevent simultaneous updates or reading
                    // while applying updates.
                    lock (dataSource)
                    {
                        dataSource.AddLogEntry(entry);
                    }
                    logEntryAdded = true;
                }
                filePosition = _reader.BaseStream.Position;
            }
            // Raise event if any log entries were added
            if (logEntryAdded)
            {
                if (this.LogChanged != null)
                {
                    this.LogChanged(this, new EventArgs());
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        private LogEntry ParseLogEntry(string line)
        {
            // Parse the line using the regex
            Match _regexMatch = RegEx.Match(line);
            if (_regexMatch.Success)
            {
                // Add this entry to the data source
                LogEntry entry = new LogEntry()
                {
                    logEntryID = Guid.NewGuid(),
                    logComputer = this.hostName,
                    logSource = this.Name,
                    eventTime = DateTime.Parse(_regexMatch.Groups["datetime"].Value),
                    message = _regexMatch.Groups["message"].Value
                };
                return entry;
            }
            else
            {
                //
                // TODO: Need to reevaluate the pattern matching for this file
                //
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private StreamReader OpenLogFile()
        {
            int counter = 0;
            StreamReader reader = null;

            // This loop continues until the file is open or the file could not
            // be open after 50 retries (1 second).
            while ((reader == null) && (counter < 50))
            {
                try
                {
                    reader = File.OpenText(filename);
                }
                catch (IOException)
                {
                    counter++;
                    Thread.Sleep(20);
                }
            }

            return reader;
        }

        #endregion Private Methods

        #region Private Properties

        /// <summary>
        /// The data source where log entries are added.
        /// </summary>
        private ILogEntryDataSource dataSource;

        /// <summary>
        /// Denotes whether the log file for this watcher is local or remote.
        /// </summary>
        private bool FileIsRemote
        {
            get
            {
                if (String.IsNullOrEmpty(filename))
                {
                    return false;
                }
                if (new Uri(filename).IsUnc)
                {
                    return true;
                }
                if (new DriveInfo(filename).DriveType == DriveType.Network)
                {
                    return true;
                }
                return false;
            }
        }

        /// <summary>
        /// The name of the log file being watched
        /// </summary>
        private string filename;

        /// <summary>
        /// The position of the last byte read in the file
        /// </summary>
        private long filePosition = 0;

        /// <summary>
        /// The FileSystemWatcher object that raises events when the file changes.
        /// </summary>
        private FileSystemWatcher fileWatcher;

        /// <summary>
        /// The host name of the computer where the log file is located.
        /// </summary>
        private string hostName;

        /// <summary>
        /// A regular expression object used to process this log file.
        /// </summary>
        private Regex _regex;
        public Regex RegEx
        {
            get
            {
                if (_regex == null)
                {
                    // Check for a preferred regular expression for this file
                    // If preferred regex found
                        // Return regex
                    // Otherwise
                        // Find a regular expression that works with this file
                }
                string regexPattern = @"(?<datetime>(Sun|Mon|Tue|Wed|Thu|Fri|Sat), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2}) (?<message>.*)";
                _regex = new Regex(regexPattern);
                return _regex;
            }
        }

        #endregion

        #region Public Properties
        /// <summary>
        /// Enabled returns true if the file is currently being watched.
        /// </summary>
        public bool Enabled
        {
            get
            {
                if (fileWatcher != null)
                {
                    return fileWatcher.EnableRaisingEvents;
                }
                return false;
            }
            set
            {
                if (fileWatcher != null)
                {
                    if (value == true)
                    {
                        // Read log from last read point to end of file
                        ReadNewLogEntries();
                    }
                    fileWatcher.EnableRaisingEvents = value;
                }
                else
                {
                    throw new NullReferenceException();
                }
            }
        }

        /// <summary>
        /// The name of the LogWatcher object.
        /// Currently this is the name of the log file.
        /// </summary>
        public string Name
        {
            get
            {
                return System.IO.Path.GetFileName(filename);
            }
        }
        #endregion

        #region FileSystemWatcher Event Handlers
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void fileWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            ReadNewLogEntries();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void fileWatcher_Created(object sender, FileSystemEventArgs e)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void fileWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void fileWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
}
